package cn.onesdream.app.api.config;

import cn.onesdream.app.common.exception.ServiceException;
import cn.onesdream.app.common.util.ErrorType;
import cn.onesdream.app.common.util.IpUtil;
import cn.onesdream.app.common.util.ResponseResult;
import cn.onesdream.app.dao.entity.SysLog;
import cn.onesdream.app.dao.mapper.SysLogMapper;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.util.stream.Collectors;

@RestControllerAdvice
@Slf4j
public class GlobalDefultExceptionHandler {
    @Autowired
    private SysLogMapper sysLogMapper;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    private static final String APPLICATION_NAME = "small_planet";

    /** 处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常，详情继续往下看代码 */
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public ResponseResult bindExceptionHandler(BindException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        ErrorType errorType = new ErrorType(message);
        return doHeadler(errorType);
    }

    /** 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public ResponseResult constraintViolationExceptionHandler(ConstraintViolationException e) {
        String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
        ErrorType errorType = new ErrorType(message);
        return doHeadler(errorType);
    }

    /** 处理请求参数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。 */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseBody
    public ResponseResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
        ErrorType errorType = new ErrorType(message);
        return doHeadler(errorType);
    }

    /**
     * 参数为空的异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public ResponseResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e){
        ErrorType errorType = new ErrorType("缺少必填参数");
        return doHeadler(errorType);
    }

    /**
     * 业务自定义异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    @ResponseBody
    public ResponseResult serviceExceptionHandler(ServiceException e){
        ErrorType errorType = e.getErrorType();
        return doHeadler(errorType);
    }

    /**
     * 未知异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseResult exceptionHandler(Exception e){
        StackTraceElement[] stackTrace = e.getStackTrace();
        String apiName = null;
        for (StackTraceElement element : stackTrace) {
            String str = element.toString();
            if(str.contains("cn.onesdream.app.")){
                apiName = str.substring(str.lastIndexOf("cn.onesdream.") + 17);
                break;
            }
        }
        ErrorType errorType = new ErrorType("500", null, apiName + ":" + e.getClass().getName()
                + ":" + e.getMessage()
                + ":" + e.getCause());
        log.error(JSON.toJSONString(errorType));
        SysLog sysLog = new SysLog(null, LocalDateTime.now(), 1, JSON.toJSONString(errorType), APPLICATION_NAME, IpUtil.getClientIpAddr() + ":" + IpUtil.getRequestUrl().substring(IpUtil.getRequestUrl().lastIndexOf(contextPath) + contextPath.length()));
        sysLogMapper.insert(sysLog);
        errorType.setMsg("系统未知异常，请联系管理员！");
        return new ResponseResult(errorType);
    }

    private ResponseResult doHeadler(ErrorType errorType){
        log.error(JSON.toJSONString(errorType));
        SysLog sysLog = new SysLog(null, LocalDateTime.now(), 1, JSON.toJSONString(errorType), APPLICATION_NAME, IpUtil.getClientIpAddr() + ":" + IpUtil.getRequestUrl().substring(IpUtil.getRequestUrl().lastIndexOf(contextPath) + contextPath.length()));
        sysLogMapper.insert(sysLog);
        return new ResponseResult(errorType);
    }
}
